/* eslint-disable no-restricted-globals */
/* eslint-disable no-case-declarations */
/* eslint-disable no-use-before-define */
/* eslint-disable no-plusplus */
/* eslint-disable no-param-reassign */
import * as _ from 'lodash';
import { IParseResult } from '../..';
import { DefaultOpts, IMonacoVersion, IParserType } from './default-opts';
import * as MyWorker from './parser.worker';
import { ICompletionItem, ITableInfo, reader, ICursorInfo } from '../sql-parser';

const supportedMonacoEditorVersion = ['0.13.2', '0.15.6'];

export function monacoSqlAutocomplete(monaco: any, editor: any, opts?: Partial<DefaultOpts>) {
  // 默认配置
  opts = _.defaults(opts || {}, new DefaultOpts(monaco));

  // 判断是否为支持的版本
  if (supportedMonacoEditorVersion.indexOf(opts.monacoEditorVersion) === -1) {
    throw Error(
      `monaco-editor version ${
        opts.monacoEditorVersion
      } is not allowed, only support ${supportedMonacoEditorVersion.join(' ')}`,
    );
  }

  // Get parser info and show error.
  let currentParserPromise: any = null;
  let editVersion = 0;

  // 监听编辑器内容更改
  editor.onDidChangeModelContent((event: any) => {
    editVersion++;
    const currentEditVersion = editVersion;

    currentParserPromise = new Promise(resolve => {
      setTimeout(() => {
        const model = editor.getModel();

        // TODO 异步解析器
        asyncParser(editor.getValue(), model.getOffsetAt(editor.getPosition()), opts.parserType).then(parseResult => {
          // 解析结果
          resolve(parseResult);

          if (currentEditVersion !== editVersion) {
            // 当前方法还未处理完成，又改变了编辑器中的内容，丢掉未处理的，进行下一次解析
            return;
          }

          opts.onParse(parseResult);

          // SQL语句解析错误
          if (parseResult.error) {
            // 错误提示
            const newReason =
              parseResult.error.reason === 'incomplete'
                ? `Incomplete, expect next input: \n${parseResult.error.suggestions
                    .map((each: any) => {
                      return each.value;
                    })
                    .join('\n')}`
                : `Wrong input, expect: \n${parseResult.error.suggestions
                    .map((each: any) => {
                      return each.value;
                    })
                    .join('\n')}`;

            const errorPosition = parseResult.error.token
              ? {
                  startLineNumber: model.getPositionAt(parseResult.error.token.position[0]).lineNumber,
                  startColumn: model.getPositionAt(parseResult.error.token.position[0]).column,
                  endLineNumber: model.getPositionAt(parseResult.error.token.position[1]).lineNumber,
                  endColumn: model.getPositionAt(parseResult.error.token.position[1]).column + 1,
                }
              : {
                  startLineNumber: 0,
                  startColumn: 0,
                  endLineNumber: 0,
                  endColumn: 0,
                };

            // TODO 根据偏移量获取位置，和errorPosition重复，并没有使用？？
            model.getPositionAt(parseResult.error.token);

            // 错误提示，@see https://github.com/microsoft/monaco-editor/issues/790
            monaco.editor.setModelMarkers(model, opts.language, [
              {
                ...errorPosition,
                message: newReason,
                // 级别：
                severity: getSeverityByVersion(monaco, opts.monacoEditorVersion),
              },
            ]);
            // end if(解析错误)
          } else {
            monaco.editor.setModelMarkers(editor.getModel(), opts.language, []);
          }
        });
      });
    });
  });

  /* 自动提示 */
  monaco.languages.registerCompletionItemProvider(opts.language, {
    // 触发字符
    triggerCharacters: ' $.:{}=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
    // 重点
    provideCompletionItems: async () => {
      const currentEditVersion = editVersion;
      const parseResult: IParseResult = await currentParserPromise;

      if (currentEditVersion !== editVersion) {
        return returnCompletionItemsByVersion([], opts.monacoEditorVersion);
      }

      const cursorInfo = await reader.getCursorInfo(parseResult.ast, parseResult.cursorKeyPath);

      const parserSuggestion = opts.pipeKeywords(parseResult.nextMatchings);

      if (!cursorInfo) {
        return returnCompletionItemsByVersion(parserSuggestion, opts.monacoEditorVersion);
      }

      switch (cursorInfo.type) {
        case 'tableField':
          const cursorRootStatementFields = await reader.getFieldsFromStatement(
            parseResult.ast,
            parseResult.cursorKeyPath,
            opts.onSuggestTableFields,
          );

          // group.fieldName
          const groups = _.groupBy(
            cursorRootStatementFields.filter(cursorRootStatementField => {
              return cursorRootStatementField.groupPickerName !== null;
            }),
            'groupPickerName',
          );

          const functionNames = await opts.onSuggestFunctionName(cursorInfo.token.value);

          return returnCompletionItemsByVersion(
            cursorRootStatementFields
              .concat(parserSuggestion)
              .concat(functionNames)
              .concat(
                groups
                  ? Object.keys(groups).map(groupName => {
                      return opts.onSuggestFieldGroup(groupName);
                    })
                  : [],
              ),
            opts.monacoEditorVersion,
          );
        case 'tableFieldAfterGroup':
          // 字段 . 后面的部分
          const cursorRootStatementFieldsAfter = await reader.getFieldsFromStatement(
            parseResult.ast,
            parseResult.cursorKeyPath as any,
            opts.onSuggestTableFields,
          );

          return returnCompletionItemsByVersion(
            cursorRootStatementFieldsAfter
              .filter((cursorRootStatementField: any) => {
                return (
                  cursorRootStatementField.groupPickerName ===
                  (cursorInfo as ICursorInfo<{ groupName: string }>).groupName
                );
              })
              .concat(parserSuggestion),
            opts.monacoEditorVersion,
          );
        case 'tableName':
          const tableNames = await opts.onSuggestTableNames(cursorInfo as ICursorInfo<ITableInfo>);

          return returnCompletionItemsByVersion(tableNames.concat(parserSuggestion), opts.monacoEditorVersion);
        case 'functionName':
          return opts.onSuggestFunctionName(cursorInfo.token.value);
        default:
          return returnCompletionItemsByVersion(parserSuggestion, opts.monacoEditorVersion);
      }
    },
  });

  monaco.languages.registerHoverProvider(opts.language, {
    provideHover: async (model: any, position: any) => {
      const parseResult: IParseResult = await asyncParser(
        editor.getValue(),
        model.getOffsetAt(position),
        opts.parserType,
      );

      const cursorInfo = await reader.getCursorInfo(parseResult.ast, parseResult.cursorKeyPath);

      if (!cursorInfo) {
        return null as any;
      }

      let contents: any = [];

      switch (cursorInfo.type) {
        case 'tableField':
          const extra = await reader.findFieldExtraInfo(
            parseResult.ast,
            cursorInfo,
            opts.onSuggestTableFields,
            parseResult.cursorKeyPath,
          );
          contents = await opts.onHoverTableField(cursorInfo.token.value, extra);
          break;
        case 'tableFieldAfterGroup':
          const extraAfter = await reader.findFieldExtraInfo(
            parseResult.ast,
            cursorInfo,
            opts.onSuggestTableFields,
            parseResult.cursorKeyPath,
          );
          contents = await opts.onHoverTableField(cursorInfo.token.value, extraAfter);
          break;
        case 'tableName':
          contents = await opts.onHoverTableName(cursorInfo as ICursorInfo);
          break;
        case 'functionName':
          contents = await opts.onHoverFunctionName(cursorInfo.token.value);
          break;
        default:
      }

      return {
        range: monaco.Range.fromPositions(
          model.getPositionAt(cursorInfo.token.position[0]),
          model.getPositionAt(cursorInfo.token.position[1] + 1),
        ),
        contents,
      };
    },
  });
}

// 实例化一个 worker
const worker: Worker = new (MyWorker as any)();

let parserIndex = 0;

/**
 * 异步解析器
 *
 * @param {string} text 待解析的SQL
 * @param {number} index 光标位置偏移量
 * @param {IParserType} parserType 解析类型
 * @return {*}
 */
const asyncParser = async (text: string, index: number, parserType: IParserType) => {
  // 解析次数：当第一次解析还未完成，触发了第二次解析，这是丢弃第一次解析
  parserIndex++;
  const currentParserIndex = parserIndex;

  let resolve: any = null;
  let reject: any = null;

  const promise = new Promise((promiseResolve, promiseReject) => {
    resolve = promiseResolve;
    reject = promiseReject;
  });

  // TODO web work
  // 发送消息：SQL语句，光标偏移量，类型如mysql、odps等
  worker.postMessage({ text, index, parserType });

  // 响应消息
  worker.onmessage = event => {
    if (currentParserIndex === parserIndex) {
      // 如果没有进行下一次的SQL解析，即：当完成这一步时没有改变编辑器的内容（触发再次解析）
      resolve(event.data);
    } else {
      reject();
    }
  };

  return promise as Promise<IParseResult>;
};

function returnCompletionItemsByVersion(value: ICompletionItem[], monacoVersion: IMonacoVersion) {
  switch (monacoVersion) {
    case '0.13.2':
      return value;
    case '0.15.6':
      return {
        suggestions: value,
      };
    default:
      throw Error('Not supported version');
  }
}

function getSeverityByVersion(monaco: any, monacoVersion: IMonacoVersion) {
  switch (monacoVersion) {
    case '0.13.2':
      return monaco.Severity.Error;
    case '0.15.6':
      return monaco.MarkerSeverity.Error;
    // TODO 其他版本
    default:
      throw Error('Not supported version');
  }
}
